package com.alogic.remote.util;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

import com.alogic.metrics.Dimensions;
import com.alogic.metrics.Fragment;
import com.alogic.metrics.Measures;
import com.alogic.metrics.Fragment.Method;
import com.alogic.metrics.impl.DefaultFragment;
import com.alogic.metrics.stream.MetricsCollector;
import com.anysoft.rrm.RRData;
import com.anysoft.rrm.RRData.Abstract;
import com.anysoft.util.Pair;

import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 远程调用的指标统计
 * 
 * @since 1.6.12.32 [20190429] <br>
 * 
 * @version 1.6.12.34 [20190509] <br>
 * - 删除不必要的警告输出;
 * 
 */
public class RemoteCallMetrics extends Abstract{
	
	public static final Logger LOG = LoggerFactory.getLogger(RemoteCallMetrics.class);

    /**
     * 总调用次数
     */
    protected long totalTimes = 0;

    /**
     * 错误的次数
     */
    protected long errorTimes = 0;

    /**
     * 平均调用时间
     */
    protected long durationAvg = -1;

    /**
     * 最大调用时间
     */
    protected long durationMax = -1;

    /**
     * 最少调用时间
     */
    protected long durationMin = -1;

    /**
     * 远程主机
     */
    protected String hostIp = "127.0.0.1";

    /**
     * 远程端口
     */
    protected String hostPort = "80";

    /**
     * 远程路径
     */
    protected String path = "/";

    protected static Pattern pattern = Pattern.compile("(\\w+):\\/\\/([^/:]+)(?::(\\d*))?([^#\\?]*)");

    public RemoteCallMetrics(String ip,String port,String path) {
        super("0");
        this.hostIp = ip;
        this.hostPort = port;
        this.path = path;
        this.setId("rpc.thpt:" + getMetricsId());
    }

    public RemoteCallMetrics(String url){
    	this(url,null);
    }
    
    public RemoteCallMetrics(String url,String path){
        super("0");
        Matcher matcher = pattern.matcher(url);
        if (matcher.find()){
            this.hostIp = matcher.group(2);
            this.hostPort = matcher.group(3);
            if (StringUtils.isEmpty(this.hostPort)){
                String scheme=matcher.group(1);
                if ("https".equals(scheme)){
                    this.hostPort = "443";
                }else{
                    this.hostPort = "80";
                }
            }
            this.path = StringUtils.isNotEmpty(path)?path:matcher.group(4);
        }
        this.setId("rpc.thpt:" + getMetricsId());
    }

	@Override
	public void listAttrs(List<Pair<String,String>> list){
		if (list != null){
			list.add(new Pair.Default<String, String>("remoteIp", this.hostIp));
			list.add(new Pair.Default<String, String>("remotePort", this.hostPort));
			list.add(new Pair.Default<String, String>("remotePath", this.path));
		}
	}
    
    protected String getMetricsId(){
        return DigestUtils.md5Hex(String.format("%s:%s:%s",this.hostIp,this.hostPort,this.path));
    }

    public void incr(RRData fragment) {
        if (fragment instanceof RemoteCallMetrics) {
            RemoteCallMetrics sm = (RemoteCallMetrics) fragment;

            if (durationAvg < 0) {
                durationAvg = sm.durationAvg;
            } else {
                durationAvg = (durationAvg * totalTimes + sm.durationAvg * sm.totalTimes)
                        / (sm.totalTimes + totalTimes);
            }

            totalTimes += sm.totalTimes;
            errorTimes += sm.errorTimes;

            if (durationMax < 0) {
                durationMax = sm.durationMax;
            } else {
                if (durationMax < sm.durationMax) {
                    durationMax = sm.durationMax;
                }
            }

            if (durationMin < 0) {
                durationMin = sm.durationMin;
            } else {
                if (durationMin > sm.durationMin) {
                    durationMin = sm.durationMin;
                }
            }
        }
    }

    public void report(Element xml) {
        if (xml != null) {
            xml.setAttribute("total", String.valueOf(totalTimes));
            xml.setAttribute("error", String.valueOf(errorTimes));
            xml.setAttribute("avg", String.valueOf(durationAvg));
            xml.setAttribute("max", String.valueOf(durationMax));
            xml.setAttribute("min", String.valueOf(durationMin));
        }
    }

    public void report(Map<String, Object> json) {
        if (json != null) {
            json.put("total", totalTimes);
            json.put("error", errorTimes);
            json.put("avg", durationAvg);
            json.put("max", durationMax);
            json.put("min", durationMin);
        }
    }

    public void count(long duration, boolean error) {
        totalTimes++;
        if (error) {
            errorTimes++;
        }
        durationAvg = duration;
        durationMax = duration;
        durationMin = duration;
    }

    public RRData copy() {
        RemoteCallMetrics other = new RemoteCallMetrics(this.hostIp,this.hostPort,this.path);
        other.durationAvg = durationAvg;
        other.durationMax = durationMax;
        other.durationMin = durationMin;
        other.totalTimes = totalTimes;
        other.errorTimes = errorTimes;
        return other;
    }

    @Override
    public void report(MetricsCollector collector) {
        if (collector != null){
            Fragment f = new DefaultFragment("rpc.thpt");
            Dimensions dims = f.getDimensions();
            if (dims != null){
                dims.set("remotePath", this.path, false);
                dims.set("remoteIp", this.hostIp, false);
                dims.set("remotePort", this.hostPort, false);
            }
            Measures meas = f.getMeasures();
            if (meas != null){
                meas.set("max",durationMax,Method.max);
                meas.set("min", durationMin,Method.min);
                meas.set("avg", durationAvg,Method.avg);
                meas.set("tms", totalTimes,Method.sum);
                meas.set("err", errorTimes,Method.sum);
            }
            collector.metricsIncr(f);
        }
    }
}